package org.rcgwt.client.ui.calendar.synchronization;

import java.util.Date;

import org.rcgwt.client.ui.calendar.RichCalendar;
import org.rcgwt.client.ui.calendar.controller.CalendarAction;
import org.rcgwt.client.ui.calendar.controller.CalendarController;
import org.rcgwt.client.ui.calendar.i18n.AtomConstantsProvider;
import org.rcgwt.client.ui.calendar.i18n.ConstantsProvider;
import org.rcgwt.client.ui.calendar.model.impl.CalendarModel;
import org.rcgwt.client.ui.calendar.model.impl.Event;
import org.rcgwt.client.ui.calendar.model.impl.EventColor;
import org.rcgwt.client.ui.calendar.model.impl.EventFrequency;
import org.rcgwt.client.ui.calendar.model.impl.EventType;
import org.rcgwt.client.util.Pattern;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.accounts.client.AuthSubStatus;
import com.google.gwt.accounts.client.User;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.gdata.client.GData;
import com.google.gwt.gdata.client.GDataSystemPackage;
import com.google.gwt.gdata.client.atom.Person;
import com.google.gwt.gdata.client.calendar.CalendarEntry;
import com.google.gwt.gdata.client.calendar.CalendarFeed;
import com.google.gwt.gdata.client.calendar.CalendarFeedCallback;
import com.google.gwt.gdata.client.calendar.CalendarService;
import com.google.gwt.gdata.client.impl.CallErrorException;
import com.google.gwt.i18n.client.DateTimeFormat;

/**
 * A Google agenda events synchronizer (using GDATA API)
 * 
 * @author Elian ORIOU
 * 
 */

@SuppressWarnings("deprecation")
public abstract class GoogleAgendaSynchronizer {

	private static final ConstantsProvider constants = GWT
			.create(ConstantsProvider.class);

	private static final AtomConstantsProvider atomConstants = GWT
			.create(AtomConstantsProvider.class);

	/* Constants */
	private static final String APP_NAME = "localhost";
	private static final String RECURRING_EVENT_PREFIX = atomConstants
			.firstStart();
	private static final String DURATION_LABEL = atomConstants.duration();
	private static final String UTC_TIME_INDICATOR = atomConstants.UTC();
	private static final String CEST_TIME_INDICATOR = atomConstants.CEST();

	/* Patterns Strings */

	private static final String SIMPLE_DATE_PATTERN = "[a-z]{3}[.] [0-9]{1,2} [a-z]{4}[.]? [0-9]{4}";
	private static final String HOUR_DATE_PATTERN = SIMPLE_DATE_PATTERN
			+ " [0-9]{2}[:][0-9]{2}";
	private static final String HOUR_PATTERN = "[0-9]{2}[:][0-9]{2}";
	private static final String ADVANCED_HOUR_PATTERN = "[0-9]{2}[:][0-9]{2}:[0-9]{2}";

	private static final String RECURRING_DATE = "[0-9]{4}[-][0-9]{2}[-][0-9]{2} "
			+ UTC_TIME_INDICATOR;
	private static final String RECURRING_DATE_TIME = "[0-9]{4}[-][0-9]{2}[-][0-9]{2} "
			+ ADVANCED_HOUR_PATTERN + " " + CEST_TIME_INDICATOR;

	/* Patterns */

	private static final Pattern WHOLE_DAYS_PATTERN = Pattern
			.compile(SIMPLE_DATE_PATTERN + " " + "[a-z]{2}" + " "
					+ SIMPLE_DATE_PATTERN);

	private static final Pattern WHOLE_DAY_PATTERN = Pattern
			.compile(SIMPLE_DATE_PATTERN);

	private static final Pattern DAYS_SLOT_PATTERN = Pattern
			.compile(HOUR_DATE_PATTERN + " " + "[a-z]{2}" + " "
					+ HOUR_DATE_PATTERN);

	private static final Pattern DAY_SLOT_PATTERN = Pattern
			.compile(HOUR_DATE_PATTERN + " " + "[a-z]{2}" + " " + HOUR_PATTERN);

	private static final Pattern RECURRING_DATE_PATTERN = Pattern
			.compile(RECURRING_DATE);
	private static final Pattern RECURRING_DATE_TIME_PATTERN = Pattern
			.compile(RECURRING_DATE_TIME);

	/* Date parsing patterns */

	private static final String HOUR = "H:m";
	private static final String SIMPLE_DATE = "E d M y";
	private static final String DATE_WITH_HOURS = "E d M y H:m";

	private static final String DATE = "y-M-d";
	private static final String ADVANCED_DATE = "y-M-d H:m:s";

	/* The component */
	private CalendarController controller;
	private CalendarModel model;

	/* Date Types */
	private int dateType;

	private static final int WHOLE_DAYS = 0;
	private static final int WHOLE_DAY = 1;
	private static final int DAYS_SLOT = 2;
	private static final int DAY_SLOT = 3;
	private static final int RECURRING_EVENT = 4;

	/* Authentication fields */

	private static final String CALENDAR_AUTHENTICATION_SCOPE = "http://www.google.com/calendar/feeds/";
	private static String CALENDAR_FEED_URI = "http://www.google.com/calendar/feeds/default/allcalendars/full";

	public GoogleAgendaSynchronizer() {
	}

	/**
	 * Synchronizes with Google Calendar
	 */

	public void synchronizeWithAuthentication(String calendarName,
			final RichCalendar cal) {
		controller = cal.getController();
		model = cal.getModel();
		dateType = -1;

		if (GData.isLoaded(GDataSystemPackage.CALENDAR)) {
			authentication();
		} else {
			GData.loadGDataApi(getAPIKey(), new Runnable() {
				public void run() {
					authentication();
				}
			}, GDataSystemPackage.CALENDAR);
		}
	}

	/**
	 * Synchronizes calendar without authentication : private calendar feed URI
	 * only : A public calendar URI required an authentication.
	 * 
	 * @param calendarFeed
	 * @param cal
	 */

	public void synchronize(final String calendarFeedURI, final RichCalendar cal) {
		controller = cal.getController();
		model = cal.getModel();
		dateType = -1;

		CALENDAR_FEED_URI = calendarFeedURI;

		if (GData.isLoaded(GDataSystemPackage.CALENDAR)) {
			getCalendars();
		} else {
			GData.loadGDataApi(null, new Runnable() {
				public void run() {
					getCalendars();
				}
			}, GDataSystemPackage.CALENDAR);
		}
	}

	/**
	 * Authenticates user
	 */

	private void authentication() {
		AuthSubStatus status = User.getStatus(CALENDAR_AUTHENTICATION_SCOPE);
		if (status == AuthSubStatus.LOGGED_IN) {
			Log.info("Google Services : Logged in !");
			getCalendars();
		} else if (status == AuthSubStatus.LOGGED_OUT) {
			Log
					.info("Google Services : Logged out ! This URI needs an authentication !");
			User.login(CALENDAR_AUTHENTICATION_SCOPE);
		}
	}

	/**
	 * Recovers calendars entries
	 */

	private void getCalendars() {
		CalendarService service = CalendarService.newInstance(APP_NAME);
		service.getAllCalendarsFeed(CALENDAR_FEED_URI,
				new CalendarFeedCallback() {
					@Override
					public void onFailure(CallErrorException caught) {
						Log
								.error("Error during the Google Calendar service call ! (feed URL is incorrect or feed requires authentication)");
					}

					@Override
					public void onSuccess(CalendarFeed result) {
						CalendarEntry[] entriesList = result.getEntries();
						try {
							for (int i = 0; i < entriesList.length; i++) {
								Event event = parseCalendarEntry(entriesList[i]);
								model.getManager().addEvent(event);
							}
						} catch (JavaScriptException e) {
							Log
									.error("The XML/ATOM feed is invalid and cannot be parsed !");
							return;
						}
						Log
								.info("Google Calendar events are imported ! (API KEY : "
										+ getAPIKey() + ")");
						controller.handle(CalendarAction.REFRESH_GRID);
					}
				});
	}

	/**
	 * Converts Calendar Entry to Calendar Event
	 * 
	 * @param entry
	 * @return
	 */

	private Event parseCalendarEntry(CalendarEntry entry)
			throws JavaScriptException {

		String title = entry.getTitle().getText();
		String content = entry.getTitle().getText();
		String summary = entry.getSummary().getText();

		String place = null;
		int duration = 0;

		for (int i = 0; i < entry.getLocations().length; i++) {
			place += entry.getLocations()[i].getLabel();
		}

		int startIndex = summary.indexOf(':') + 2;
		int endIndex = summary.indexOf('&');
		if (startIndex > 10) {
			startIndex = 0;
		}
		if (endIndex == -1) {
			endIndex = summary.substring(startIndex).indexOf('<') + startIndex;
		}
		if (summary.contains(RECURRING_EVENT_PREFIX) == true
				|| summary.contains(RECURRING_EVENT_PREFIX) == true) {
			startIndex = summary.indexOf(':') + 2;
			endIndex = summary.substring(startIndex).indexOf('<') + startIndex;
			if (summary.contains(DURATION_LABEL) == true) {
				int begin = summary.indexOf(DURATION_LABEL)
						+ DURATION_LABEL.length() + 3;
				duration = Integer
						.parseInt(summary.substring(begin, begin + 4));
			} else if (summary.contains(DURATION_LABEL) == true) {
				int begin = summary.indexOf(DURATION_LABEL)
						+ DURATION_LABEL.length() + 3;
				duration = Integer
						.parseInt(summary.substring(begin, begin + 4));
			}
		}
		String dates = summary.substring(startIndex, endIndex);

		if (DAYS_SLOT_PATTERN.matches(dates) == true) {
			dateType = DAYS_SLOT;
		} else if (DAY_SLOT_PATTERN.matches(dates) == true) {
			dateType = DAY_SLOT;
		} else if (WHOLE_DAYS_PATTERN.matches(dates) == true) {
			dateType = WHOLE_DAYS;
		} else if (WHOLE_DAY_PATTERN.matches(dates) == true) {
			dateType = WHOLE_DAY;
		} else if (RECURRING_DATE_PATTERN.matches(dates) == true
				|| RECURRING_DATE_TIME_PATTERN.matches(dates) == true) {
			dateType = RECURRING_EVENT;
		}

		Date start = parseBeginDate(dates);
		Date end = parseEndDate(dates, start);

		/* Frequency Management */
		EventFrequency frequency = EventFrequency.NONE;
		if (dateType == RECURRING_EVENT) {
			switch (duration) {
			case 0:
				frequency = EventFrequency.NONE;
				break;
			case 3500:
				frequency = EventFrequency.EVERYDAY;
				break;
			case 3600:
				frequency = EventFrequency.EVERYMONTH;
				break;
			case 8640:
				frequency = EventFrequency.EVERYYEAR;
				break;
			default:
				frequency = EventFrequency.NONE;
			}
		}
		Event e = new Event(title, content, start, end, place,
				EventType.G_SYNCHRONIZED, frequency, EventColor.NONE);
		for (int i = 0; i < entry.getAuthors().length; i++) {
			Person p = entry.getAuthors()[i];
			e.addParticipant(p.getName().getValue() + " ["
					+ p.getEmail().getValue() + "]");
		}
		return e;
	}

	/**
	 * Parses retrieved begin date from Google Calendar
	 * 
	 * @param content
	 * @return
	 */

	private Date parseBeginDate(String content) {
		String dateFormat = SIMPLE_DATE;
		int endIndex = 0;
		if (dateType == WHOLE_DAYS) {
			endIndex = content.indexOf(constants.to()) - 1;
		} else if (dateType == WHOLE_DAY) {
			endIndex = content.length();
		} else if (dateType == DAYS_SLOT || dateType == DAY_SLOT) {
			endIndex = content.indexOf(constants.to()) - 1;
			dateFormat = DATE_WITH_HOURS;
		} else if (dateType == RECURRING_EVENT) {
			if (RECURRING_DATE_PATTERN.matches(content) == true) {
				endIndex = content.indexOf(UTC_TIME_INDICATOR) - 1;
				dateFormat = DATE;
			} else if (RECURRING_DATE_TIME_PATTERN.matches(content) == true) {
				endIndex = content.indexOf(CEST_TIME_INDICATOR) - 1;
				dateFormat = ADVANCED_DATE;
			} else {
				Log.error("ATOM feed format is not valid !");
			}
		}
		return DateTimeFormat.getFormat(dateFormat).parse(
				content.substring(0, endIndex));
	}

	/**
	 * Parses retrieved end date from Google Calendar
	 * 
	 * @param content
	 * @return
	 */

	private Date parseEndDate(String content, Date begin) {
		int beginIndex = content.indexOf(constants.to()) + 3;
		int endIndex = content.length();
		String dateFormat = SIMPLE_DATE;

		if (dateType == WHOLE_DAY) {
			return begin;
		} else if (dateType == DAYS_SLOT) {
			dateFormat = DATE_WITH_HOURS;
		} else if (dateType == DAY_SLOT) {
			dateFormat = HOUR;
			String subcontent = content.substring(beginIndex, endIndex);
			Date date = DateTimeFormat.getFormat(dateFormat).parse(subcontent);
			Date d = (Date) begin.clone();
			d.setHours(date.getHours());
			d.setMinutes(date.getMinutes());
			d.setSeconds(date.getSeconds());
			return d;
		} else if (dateType == RECURRING_EVENT) {
			return begin;
		}

		String subcontent = content.substring(beginIndex, endIndex);
		return DateTimeFormat.getFormat(dateFormat).parse(subcontent);
	}

	/**
	 * Returns the GData API Key (to use it)
	 * 
	 * @return
	 */

	public abstract String getAPIKey();
}
